package com.xinghanlaser.applet.base.oss.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.*;
import com.amazonaws.util.IOUtils;
import com.xinghanlaser.applet.base.common.exception.Assert;
import com.xinghanlaser.applet.base.common.exception.BizException;
import com.xinghanlaser.applet.base.oss.config.OssProperties;
import com.xinghanlaser.applet.base.oss.model.*;
import com.xinghanlaser.applet.base.oss.service.OssService;
import com.xinghanlaser.applet.base.oss.utils.OssTemplateUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @description OSS接口实现类
 * @author 阿爷
 * @createDate  2023/8/28
 */
public class OssServiceImpl implements OssService {
    /**
     * OSS相关配置类
     */
    private final OssProperties ossProperties;

    /**
     * S3协议对象存储模型接口
     */
    private AmazonS3 amazonS3;

    public OssServiceImpl(OssProperties ossProperties) {
        this.ossProperties = ossProperties;
    }

    /**
     * 在创建Bean对象后调用
     * 用于通过配置文件构建对应的OSS对象
     */
    @Override
    public void afterPropertiesSet(){
        //配置客户端
        ClientConfiguration clientConfiguration = new ClientConfiguration();
        clientConfiguration.setMaxConnections(ossProperties.getMaxConnections());

        //配置AWS(Amazon Web Services)客户端
        AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
                ossProperties.getEndpoint(),
                ossProperties.getRegion()
        );

        //配置认证信息
        AWSCredentials awsCredentials = new BasicAWSCredentials(
                ossProperties.getAccessKey(),
                ossProperties.getSecretKey()
        );

        AWSCredentialsProvider awsCredentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);

        //构建S3对象
        this.amazonS3 = AmazonS3Client.builder()
                .withEndpointConfiguration(endpointConfiguration)
                .withClientConfiguration(clientConfiguration)
                .withCredentials(awsCredentialsProvider)
                //.disableChunkedEncoding() //禁用分块编码
                .build();
    }

    //========================= 【桶 相关操作】 =========================
    @Override
    public boolean existBucket(String bucketName) {
        return amazonS3.doesBucketExistV2(bucketName);
    }

    @Override
    public void createBucket(String bucketName) {
        //校验桶是否存在
        if (existBucket(bucketName)) {
            throw new BizException("当前桶名已存在");
        }

        //创建桶
        amazonS3.createBucket(bucketName);
    }

    @Override
    public Set<Bucket> getAllBucket() {
        return new HashSet<>(amazonS3.listBuckets());
    }

    @Override
    public Bucket getBucket(String bucketName) {
        return amazonS3.listBuckets()
                .stream()
                .filter(b -> b.getName().equals(bucketName))
                .findFirst()
                .orElseThrow(() -> new BizException(bucketName + "桶不存在"));
    }

    @Override
    public void removeBucket(String bucketName) {
        //校验桶是否存在
        if (existBucket(bucketName)) {
            //删除桶
            amazonS3.deleteBucket(bucketName);
        }

        throw new BizException("桶不存在");
    }

    //========================= 【文件 相关操作】 =========================

    //========= 【查询】 =========
    @Override
    public boolean existObject(String objectName, String bucketName) {
        return amazonS3.doesObjectExist(bucketName, objectName);
    }

    @Override
    public boolean existObject(String objectName) {
        return existObject(objectName,ossProperties.getBucketName());
    }

    @Override
    public String getPresignedObjectUrl(String objectName) {
        return getPresignedObjectUrl(objectName,ossProperties.getBucketName());
    }

    @Override
    public String getPresignedObjectUrl(String objectName, String bucketName) {
        //判断对象是否存在
        if(!existObject(objectName,bucketName)){
            throw new BizException("对象不存在");
        }

        //获取地址
        String url = amazonS3.generatePresignedUrl(
                bucketName,
                objectName,
                null
        )
        .toString();

        //判断访问权限类型
         String policyText = amazonS3.getBucketPolicy(bucketName).getPolicyText();
        JSONArray statement = JSON.parseArray(
                JSON.toJSONString(
                    JSON.parseObject(policyText).get("Statement")
                )
        );

        //private
        if (statement.isEmpty()) {
            return url;
        }

        //public
        return url.substring(0, url.indexOf("?"));
    }

    @Override
    public List<ObjectInfo> getObjectsByPrefix(String bucketName, String prefix) {
        return Optional.ofNullable(amazonS3.listObjectsV2(bucketName, prefix))
                .map(ListObjectsV2Result::getObjectSummaries)
                .filter(CollectionUtil::isNotEmpty)
                .map(list ->
                        list.stream()
                                .map(o -> ObjectInfo.builder()
                                        .objectName(o.getKey())
                                        .size(o.getSize())
                                        .build()
                                )
                                .collect(Collectors.toList())
                )
                .orElse(ListUtil.empty());
    }

    @Override
    public List<ObjectInfo> getObjectsByPrefixOfDefaultBucket(String prefix) {
        return getObjectsByPrefix(ossProperties.getBucketName(),prefix);
    }

    @Override
    public List<ObjectInfo> getAllObjectOfBucket(String bucketName) {
        return getObjectsByPrefix(bucketName,null);
    }

    @Override
    public List<ObjectInfo> getAllObjectOfDefaultBucket() {
        return getAllObjectOfBucket(ossProperties.getBucketName());
    }


    //========= 【上传】 =========
    @Override
    public UploadObjectResult putObject(MultipartFile object) {
        return putObject(object, ossProperties.getBucketName());
    }

    @Override
    public UploadObjectResult putObject(MultipartFile object, String bucketName) {
        //校验对象是否存在
        Assert.notNull(object);

        //获取文件参数
        String originalFileName = object.getOriginalFilename();
        String objectName = OssTemplateUtil.getObjectName(originalFileName);

        try(InputStream inputStream = object.getInputStream()){
            byte[] bytes = IOUtils.toByteArray(inputStream);
            ObjectMetadata objectMetadata = new ObjectMetadata();
            objectMetadata.setContentLength(object.getSize());
            objectMetadata.setContentType(object.getContentType());
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);

            //上传对象
            PutObjectResult putObjectResult = amazonS3.putObject(
                    bucketName,
                    objectName,
                    byteArrayInputStream,
                    objectMetadata
            );

            //封装并返回成功响应
            return UploadObjectResult.builder()
                    .originName(originalFileName)
                    .objectName(objectName)
                    .bucketName(bucketName)
                    .objectUrl(getPresignedObjectUrl(objectName,bucketName))
                    .size(object.getSize())
                    .build();

        }catch (Exception e){
            throw new BizException(e.getMessage());
        }
    }

    @Override
    public UploadObjectResult putObject(InputStream objectInputStream, String originalFileName, Long size, String contextType) {
        return putObject(
                objectInputStream,
                ossProperties.getBucketName(),
                originalFileName,
                size,
                contextType
        );
    }

    @Override
    public UploadObjectResult putObject(InputStream objectInputStream, String bucketName, String originalFileName, Long size, String contextType) {
        try{
            String objectName = OssTemplateUtil.getObjectName(originalFileName);

            byte[] bytes = IOUtils.toByteArray(objectInputStream);
            ObjectMetadata objectMetadata = new ObjectMetadata();
            objectMetadata.setContentLength(size);
            objectMetadata.setContentType(contextType);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);

            //上传对象
            PutObjectResult putObjectResult = amazonS3.putObject(
                    bucketName,
                    objectName,
                    byteArrayInputStream,
                    objectMetadata
            );

            //封装并返回成功响应
            return UploadObjectResult.builder()
                    .originName(originalFileName)
                    .objectName(objectName)
                    .bucketName(bucketName)
                    .objectUrl(getPresignedObjectUrl(objectName,bucketName))
                    .size(size)
                    .build();

        }catch (Exception e){
            throw new BizException(e.getMessage());
        }
    }


    @Override
    public UploadObjectResult putObject(File object) {
        return putObject(object,ossProperties.getBucketName());
    }

    @Override
    public UploadObjectResult putObject(File object, String bucketName) {
        //校验对象是否存在
        if (ObjectUtil.isNull(object)) {
            throw new BizException("object不能为空");
        }

        //获取对象参数
        String originalFileName = object.getName();
        String objectName = OssTemplateUtil.getObjectName(originalFileName);
        Long size = object.length();
        String contentType = FileUtil.getType(object);

        try(InputStream inputStream = new FileInputStream(object)){
            byte[] bytes = IOUtils.toByteArray(inputStream);
            ObjectMetadata objectMetadata = new ObjectMetadata();
            objectMetadata.setContentLength(size);
            objectMetadata.setContentType(contentType);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);

            //上传对象
            PutObjectResult putObjectResult = amazonS3.putObject(
                    bucketName,
                    objectName,
                    byteArrayInputStream,
                    objectMetadata
            );

            //封装并返回成功响应
            return UploadObjectResult.builder()
                    .originName(originalFileName)
                    .objectName(objectName)
                    .bucketName(bucketName)
                    .objectUrl(getPresignedObjectUrl(objectName,bucketName))
                    .size(size)
                    .build();

        }catch (Exception e){
            throw new BizException(e.getMessage());
        }
    }

    @Override
    public List<UploadObjectResult> putObjects(List<MultipartFile> objects, String bucketName) {
        return Optional.ofNullable(objects)
                .filter(CollectionUtil::isNotEmpty)
                .map(list ->
                        list.stream()
                            .map(o -> putObject(o,bucketName))
                            .collect(Collectors.toList())
                )
                .orElse(ListUtil.empty());
    }

    @Override
    public List<UploadObjectResult> putObjects(List<MultipartFile> objects) {
        return putObjects(objects,ossProperties.getBucketName());
    }

    @Override
    public List<UploadObjectResult> putObjectsOfFile(List<File> objects, String bucketName) {
        return Optional.ofNullable(objects)
                .filter(CollectionUtil::isNotEmpty)
                .map(list ->
                        list.stream()
                            .map(o -> putObject(o,bucketName))
                            .collect(Collectors.toList())
                )
                .orElse(ListUtil.empty());
    }

    @Override
    public List<UploadObjectResult> putObjectsOfFile(List<File> objects) {
        return putObjectsOfFile(objects,ossProperties.getBucketName());
    }


    //========= 【下载】 =========
    @Override
    public ResponseEntity<byte[]> getObject(String objectName, String bucketName) {
        try(
                S3ObjectInputStream inputStream = amazonS3.getObject(bucketName, objectName).getObjectContent();
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ) {
            org.apache.tomcat.util.http.fileupload.IOUtils.copy(inputStream,outputStream);
            byte[] bytes = outputStream.toByteArray();
            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-Disposition",
                    "attachment;filename=" + URLEncoder.encode(objectName,
                            "UTF-8"
                    ));
            headers.setContentLength(bytes.length);
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setAccessControlExposeHeaders(Arrays.asList("*"));

            return new ResponseEntity<byte[]>(bytes, headers, HttpStatus.OK);
        }catch (Exception e){
            throw new BizException(e.getMessage());
        }
    }

    @Override
    public ResponseEntity<byte[]> getObject(String objectName) {
        return getObject(objectName,ossProperties.getBucketName());
    }

    @Override
    public S3Object getObjectInfo(String objectName, String bucketName) {
        return amazonS3.getObject(bucketName, objectName);
    }

    @Override
    public S3Object getObjectInfo(String objectName) {
        return getObjectInfo(objectName,ossProperties.getBucketName());
    }

    //========= 【删除】 =========
    @Override
    public void deleteObject(String objectName, String bucketName) {
        amazonS3.deleteObject(bucketName,objectName);
    }

    @Override
    public void deleteObject(String objectName) {
        amazonS3.deleteObject(ossProperties.getBucketName(),objectName);
    }

    @Override
    public void deleteObjects(List<String> objectNames, String bucketName) {
        Optional.ofNullable(objectNames)
                .filter(CollectionUtil::isNotEmpty)
                .ifPresent(list ->
                        list.stream().forEach(o -> deleteObject(o,bucketName))
                );
    }

    @Override
    public void deleteObjects(List<String> objectNames) {
        deleteObjects(objectNames,ossProperties.getBucketName());
    }

    @Override
    public AmazonS3 getAmazonS3() {
        return this.amazonS3;
    }


    @Override
    public InitBigFileResult initBigFileUpload(String bucketName, String originalFileName){
        if(!existBucket(bucketName)){
            throw new BizException("桶不存在");
        }

        String objectName = OssTemplateUtil.getObjectName(originalFileName);

        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectName);
        request.setRequesterPays(Boolean.TRUE);
        InitiateMultipartUploadResult result = amazonS3.initiateMultipartUpload(request);

        return InitBigFileResult.builder()
                .uploadId(result.getUploadId())
                .objectName(objectName)
                .build();
    }

    @Override
    public PartETag uploadPart(BigFileUploadPartParam param){
        if(!existBucket(param.getBucketName())){
            throw new BizException("桶不存在");
        }
        return Optional.ofNullable(param.getObjectPart())
                .map(objectPart -> {
                    UploadPartRequest request = new UploadPartRequest();

                    request.setBucketName(param.getBucketName());
                    request.setPartNumber(param.getPartNumber());
                    request.setLastPart(param.getLastPart());
                    request.setRequesterPays(Boolean.TRUE);

                    try {
                        request.setInputStream(objectPart.getInputStream());
                    } catch (IOException e) {
                        throw new BizException("获取Object输入流错误");
                    }

                    request.setPartSize(objectPart.getSize());
                    request.setFileOffset(0);
                    request.setObjectMetadata(new ObjectMetadata());
                    request.setKey(param.getObjectName());
                    request.setUploadId(param.getUploadId());

                    UploadPartResult result = amazonS3.uploadPart(request);

                    return result.getPartETag();
                })
                .orElse(null);
    }

    @Override
    public boolean completeBigFileUpload(BigFileUploadCompleteParam param){
        if(!existBucket(param.getBucketName())){
            throw new BizException("桶不存在");
        }

        CompleteMultipartUploadRequest request = new CompleteMultipartUploadRequest();
        request.setUploadId(param.getUploadId());
        request.setBucketName(param.getBucketName());
        request.setKey(param.getObjectName());

        List<PartETag> eTagList = param.getPartETagList()
                .stream()
                .map(x -> BeanUtil.copyProperties(x, PartETag.class))
                .collect(Collectors.toList());
        request.setPartETags(eTagList);

        CompleteMultipartUploadResult result = amazonS3.completeMultipartUpload(request);

        return ObjectUtil.isNotNull(result);
    }

    @Override
    public void abortBigFileUpload(String objectKey, String uploadId) {
        abortBigFileUpload(ossProperties.getBucketName(), objectKey, uploadId);
    }

    @Override
    public void abortBigFileUpload(String bucketName, String objectKey, String uploadId) {
        AbortMultipartUploadRequest request = new AbortMultipartUploadRequest(bucketName, objectKey, uploadId);
        amazonS3.abortMultipartUpload(request);
    }
}
